package com.gitee.huanminabc.jcommon.reflect;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 扫描指定包下的所有类
 * <p>
 * 1.支持扫描jar包和文件系统
 * 2.支持过滤器
 * 3.支持多线程
 */
public class ClassScanner {

    public static Set<Class<?>> scanPackGetClasses(String packageName) {
        return scanPackGetClasses(packageName, null);
    }

    public static Set<Class<?>> scanPackGetClasses(String packageName, Predicate<Class<?>> predicate) {
        Set<Class<?>> classes;
        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replace(".", "/"));
            List<List<Class<?>>> list = new ArrayList<>();

            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if ("file".equals(url.getProtocol())) {
                    // 文件系统路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    list.add(scanPackInDirectory(packageName, filePath, predicate));
                } else if ("jar".equals(url.getProtocol())) {
                    // JAR 文件路径
                    JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                    list.add(scanPackInJar(packageName, jarFile, predicate));
                }
            }

            // 合并所有流并收集结果
            classes = list.stream()
                    .flatMap(Collection::stream)
                    .collect(Collectors.toSet());

        } catch (IOException e) {
            throw new RuntimeException("Failed to scan classes", e);
        }
        return classes;
    }

    private static List<Class<?>> scanPackInDirectory(String packageName, String directoryPath, Predicate<Class<?>> predicate) throws IOException {
        File directory = new File(directoryPath);
        if (!directory.exists() || !directory.isDirectory()) {
            return Collections.emptyList();
        }

        return Arrays.stream(Objects.requireNonNull(directory.listFiles()))
                .parallel()
                .flatMap(file -> {
                    if (file.isDirectory()) {
                        try {
                            return scanPackInDirectory(packageName + "." + file.getName(), file.getAbsolutePath(), predicate).stream();
                        } catch (IOException e) {
                            throw new RuntimeException("Failed to scan directory: " + file.getAbsolutePath(), e);
                        }
                    } else if (file.getName().endsWith(".class")) {
                        String className = packageName + "." + file.getName().substring(0, file.getName().length() - 6);
                        try {
                            //不能使用Class.forName会导致触发类的静态代码块
                            Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
                            return (predicate == null || predicate.test(clazz)) ? Stream.of(clazz) : Stream.empty();
                        } catch (ClassNotFoundException e) {
                            throw new RuntimeException("Failed to load class: " + className, e);
                        }
                    }
                    return Stream.empty();
                })
                .collect(Collectors.toList());
    }

    private static List<Class<?>> scanPackInJar(String packageName, JarFile jarFile, Predicate<Class<?>> predicate) {
        return jarFile.stream()
                .parallel()
                .filter(entry -> entry.getName().endsWith(".class") && entry.getName().startsWith(packageName.replace(".", "/")))
                .map(entry -> {
                    String className = entry.getName().replace("/", ".").substring(0, entry.getName().length() - 6);
                    try {
                        //不能使用Class.forName会导致触发类的静态代码块
                        Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
                        return (predicate == null || predicate.test(clazz)) ? clazz : null;
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException("Failed to load class: " + className, e);
                    }
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }








    /**
     * 有待优化
     * 获取jar包中的所有类，包括jar包中的jar包
     * @param allClasses 保存类的集合
     * @param file jar包文件
     */
    public static void getJarClassAll(Set<String> allClasses, File file) {

        if (file.exists() && file.getName().endsWith(".jar")) {
            try  {
                JarFile jarFile = new JarFile(file);
                Enumeration<JarEntry> entries = jarFile.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    if (entry.getName().endsWith(".class")) {
                        String className = entry.getName().substring(0, entry.getName().length() - ".class".length()).replace("/", ".");
                        allClasses.add(className);
                    }
                    if (entry.getName().endsWith(".jar")) {
                        InputStream inputStream = jarFile.getInputStream(entry);
                        getJarClassAll(allClasses, inputStream);
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
    private static void getJarClassAll(Set<String> allClasses, InputStream inputStream) {
        try  {
            JarInputStream jarInputStream = new JarInputStream(inputStream);
            JarEntry entry = jarInputStream.getNextJarEntry();
            while (entry != null) {
                if (entry.getName().endsWith(".class")) {
                    String className = entry.getName().replace("/", ".").substring(0, entry.getName().length() - ".class".length());
                    allClasses.add(className);
                }
                if (entry.getName().endsWith(".jar")) {
                    getJarClassAll(allClasses, jarInputStream);
                }
                entry = jarInputStream.getNextJarEntry();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}